'use strict';

var fs = require('fs');
var path = require('path');
var uuidins = require('uuid');
var crypto = require('crypto');
var coreUtilIs = require('core-util-is');
var EventEmitter = require('events');
var fetch = require('node-fetch');
var child_process = require('child_process');
var nodemailer = require('nodemailer');
var sharp = require('sharp');
var log4js = require('log4js');

function _interopNamespaceDefault(e) {
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n.default = e;
  return Object.freeze(n);
}

var uuidins__namespace = /*#__PURE__*/_interopNamespaceDefault(uuidins);

//获得一个36的uuid字符串
const uuid = (version) => {
  if (version === 'v1') return uuidins__namespace.v1();
  return uuidins__namespace.v4()
};

//对字符串进行md5加密
const md5 = (str) => {
  return crypto.createHash('md5').update(str + '', 'utf8').digest('hex');
};

//延时
const sleep = time => {
  return new Promise((resolve) => {
    setTimeout(resolve, time);
  });
};

/**
 * get datetime
 * @param  {Date} date []
 * @return {String}      []
 */
function datetime(date = new Date(), format) {
  if (date && coreUtilIs.isString(date)) {
    const dateString = date;
    date = new Date(Date.parse(date));

    if (isNaN(date.getTime()) && !format) {
      format = dateString;
      date = new Date();
    }
  }
  format = format || 'YYYY-MM-DD HH:mm:ss';

  const fn = d => {
    return ('0' + d).slice(-2);
  };

  const d = new Date(date);
  const formats = {
    YYYY: d.getFullYear(),
    MM: fn(d.getMonth() + 1),
    DD: fn(d.getDate()),
    HH: fn(d.getHours()),
    mm: fn(d.getMinutes()),
    ss: fn(d.getSeconds())
  };

  return format.replace(/([a-z])\1+/ig, a => {
    return formats[a] || a;
  });
}


/**
 * get human readable file size
 * @param {Number/String} b file size
 * @param {Number} precision 
 */
function humanSize(bytes, precision = 2) {
  const b = parseInt(bytes, 10);
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  if (b === 0) { return '0 KB' }
  const i = parseInt(Math.floor(Math.log(b) / Math.log(1024)), 10);
  if (i === 0) { return `${b} ${sizes[i]}` }
  return `${(b / (1024 ** i)).toFixed(precision)} ${sizes[i]}`
}

/**
   * get human time
   * @param {Number} seconds 
   * @param {Object} param1 prefix/suffix
   * @returns 
   */
function getHumanTime(seconds, params = { prefix : '', suffix : '' }) {
  let result = '';
  let hours = '';
  let minutes = '';
  let secs = seconds || 0;
  const i = {
    gt1d: '> 1 day',
    hour: 'h',
    minute: 'm',
    second: 's'
  };

  if (secs <= 0) {
    return ''
  }
  if (secs > 86400) {
    return `${params.prefix}${i.gt1d}${params.suffix}`
  }
  if (secs > 3600) {
    hours = `${Math.floor(secs / 3600)}${i.hour} `;
    secs %= 3600;
  }
  if (secs > 60) {
    minutes = `${Math.floor(secs / 60)}${i.minute} `;
    secs %= 60;
  }
  secs += i.second;
  result = hours + minutes + secs;
  return result ? `${params.prefix}${result}${params.suffix}` : result
}

const rs2ws = (rs, ws) => {
  return new Promise((resolve, reject) => {
    ws.on('close', () => {
      resolve();
    });
    rs.pipe(ws);
  });
};

var common = /*#__PURE__*/Object.freeze({
  __proto__: null,
  datetime: datetime,
  getHumanTime: getHumanTime,
  humanSize: humanSize,
  isArray: coreUtilIs.isArray,
  isBoolean: coreUtilIs.isBoolean,
  isBuffer: coreUtilIs.isBuffer,
  isDate: coreUtilIs.isDate,
  isError: coreUtilIs.isError,
  isFunction: coreUtilIs.isFunction,
  isNull: coreUtilIs.isNull,
  isNullOrUndefined: coreUtilIs.isNullOrUndefined,
  isNumber: coreUtilIs.isNumber,
  isObject: coreUtilIs.isObject,
  isPrimitive: coreUtilIs.isPrimitive,
  isRegExp: coreUtilIs.isRegExp,
  isString: coreUtilIs.isString,
  isSymbol: coreUtilIs.isSymbol,
  isUndefined: coreUtilIs.isUndefined,
  md5: md5,
  rs2ws: rs2ws,
  sleep: sleep,
  uuid: uuid
});

/**
 * create folder 
 * @param {String} folderPath 
 */
function mkdir(folderPath) {
  if (fs.existsSync(folderPath)) {
    return true;
  }  
  if (path.extname(folderPath) != '') {
    folderPath = path.dirname(folderPath);
  }
  fs.mkdirSync(folderPath, { recursive: true });
  return true;
}

function existsFile(filePath) {
  return fs.existsSync(filePath);
}
function isFile(filePath) {
  if (existsFile(filePath)) {
    let stat = fs.statSync(filePath);
    return stat.isFile();  
  }
  throw new Error(`File is not exists:${filePath}`);
}
/**
 * delete folder by force
 * @param {String} folderPath 
 */
function deldir(folderPath) {
  if (!existsFile(folderPath)) {
    return true;
  }
  try {
    if (isFile(folderPath)) {
      fs.unlinkSync(folderPath);
    } else {
      let list = fs.readdirSync(folderPath);
      for (let i = 0; i < list.length;i++){
        let tmpFilePath = path.join(folderPath, list[i]);
        if (isFile(tmpFilePath)) {
          fs.unlinkSync(tmpFilePath);
        } else {
          deldir(tmpFilePath);
        }
      }
      fs.rmdirSync(folderPath);
    }  
    return true;
  } catch (e) {
    return false;  
  }
}

/**
 * get files in dir
 * @param {String} dir 
 * @param {String} prefix default empty ,for recursion
 * @returns 
 */
function listFiles(dir, prefix = '') {
  dir = path.normalize(dir);
  if (!fs.existsSync(dir)) return [];
  const files = fs.readdirSync(dir);
  let result = [];
  files.forEach(item => {
    const currentDir = path.join(dir, item);
    const stat = fs.statSync(currentDir);
    if (stat.isFile()) {
      result.push(path.join(prefix, item));
    } else if (stat.isDirectory()) {
      const cFiles = listFiles(currentDir, path.join(prefix, item));
      result = result.concat(cFiles);
    }
  });
  return result;
}
const getMd5 = filePath => {
  let md5 = crypto.createHash('md5');
  return new Promise(r => {
    if (fs.existsSync(filePath)) {
      let rs = fs.createReadStream(filePath);
      rs.on('data', data => {
        md5.update(data);
      });
      rs.on('close', () => {
        let rst = md5.digest('hex').toLowerCase();
        r(rst);
      });
    } else {
      r('');
    }
  })
};
/**
 * get base64 of file
 * @param {String} filepath 
 * @returns 
 */
const base64 = (filepath) => {
  return new Promise(function (resolve, reject) {
    fs.readFile(filepath, function (err, content) {
      if (err) {
        reject(err);
      } else {
        let str = content.toString('base64');
        resolve(str);
      }
    });
  });
};

var fileutil = /*#__PURE__*/Object.freeze({
  __proto__: null,
  base64: base64,
  deldir: deldir,
  existsFile: existsFile,
  getMd5: getMd5,
  isFile: isFile,
  listFiles: listFiles,
  mkdir: mkdir
});

// ffmpeg about video / audio
// Installed ffmpeg on system.


//grab image from video at time
const getImage = (videoFilePath, jpgFilePath, time = 1, wh) => {
  return new Promise((r, j) => {
    let cmdarr = ['-y','-i', videoFilePath, '-y', '-f', 'image2', '-ss', time, '-t', '10'];
    if (wh) {
      cmdarr.push('-s');
      cmdarr.push(wh);
    }
    cmdarr.push(jpgFilePath);
    let ls = child_process.spawn('ffmpeg', cmdarr, {});
    ls.on('close', code => {
      r();
    });
    ls.on('error', err => {
      j(err);
    });
  })
};
/**
 * create m3u8 from video
 */
const getM3u8 = (videoFilePath,m3u8Folder,hlsTime=5)=>{
  mkdir(m3u8Folder);
  let filePath = path.join(m3u8Folder, 'index.m3u8');
  //开始转换
  return new Promise((r, j) => {
    let ls = child_process.spawn('ffmpeg', ['-y','-i', videoFilePath, '-c:v', 'libx264', '-c:a', 'aac', '-strict','-2','-hls_list_size','0','-hls_time',hlsTime,'-f','hls',filePath], {});
    ls.on('close', () => {
      r();
    });
    ls.on('error', err => {
      j(err);
    });
  })
};
/**
 * split video 
 * @param {String} videoFilePath 
 * @param {Number} start second
 * @param {Number} time second
 * @param {String} storeFilePath 
 */
const getVideo = (videoFilePath,start=0,time=10,storeFilePath) => {
  //开始转换
  mkdir(path.dirname(storeFilePath));
  return new Promise((r, j) => {
    let ls = child_process.spawn('ffmpeg', ['-y','-i', videoFilePath, '-ss', start, '-t', time,storeFilePath], {});
    ls.on('close', () => {
      r();
    });
    ls.on('error', err => {
      j(err);
    });
  })
};
/**
 * 
 * @param {String} collapseFile file.txt - file 1.ts\nfile 2.ts
 * @param {*} storeFilePath 
 * @returns 
 */
const collapse = (collapseFilePath, storeFilePath) => {
  collapseFilePath = collapseFilePath.replace(/\\/g, '/');
  storeFilePath = storeFilePath.replace(/\\/g, '/');
  return new Promise((r, j) => {
    let ls = child_process.spawn('ffmpeg', ['-y','-f', 'concat', '-safe', 0, '-i', collapseFilePath, storeFilePath], {});
    ls.on('close', () => { r(); });
    ls.on('error', err => { j(err); });
  });
};

var ffmpeg = /*#__PURE__*/Object.freeze({
  __proto__: null,
  collapse: collapse,
  getImage: getImage,
  getM3u8: getM3u8,
  getVideo: getVideo
});

const useragent = [
  "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60",
  "Opera/8.0 (Windows NT 5.1; U; en)",
  "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50",
  "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50",
  "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
  "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
  "Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10",
  "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0",
  "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10",
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv,2.0.1) Gecko/20100101 Firefox/4.0.1",
  "Mozilla/5.0 (Windows NT 6.1; rv,2.0.1) Gecko/20100101 Firefox/4.0.1",
  "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
  "MAC：Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36",
  "Windows：Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
  "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
  "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
  "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
  "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36",
  "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
  "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16",
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
  "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36",
  "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",
  "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
  "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
  "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
  "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
  "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
  "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
  "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 SE 2.X MetaSr 1.0",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; SE 2.X MetaSr 1.0)",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)",
  "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.4.3.4000 Chrome/30.0.1599.101 Safari/537.36",
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
  "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 UBrowser/4.0.3214.0 Safari/537.36",
  "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.4094.1 Safari/537.36",
  "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
  "Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
  "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
  "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
  "Mozilla/5.0 (Linux; U; Android 2.2.1; zh-cn; HTC_Wildfire_A3333 Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
  "Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
  "MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
  "Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10",
  "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13",
  "Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+",
  "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0",
  "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
  "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)",
  "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)",
  "Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
  "Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124",
  "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)",
  "UCWEB7.0.2.37/28/999",
  "NOKIA5700/ UCWEB7.0.2.37/28/999",
  "Openwave/ UCWEB7.0.2.37/28/999",
  "Openwave/ UCWEB7.0.2.37/28/999",
];

const getRandom = ()=>{
  return useragent[Math.floor(Math.random()*(useragent.length-1))];
};

const devices = (agent) => {
  agent = agent ? agent.toLowerCase() : '';
  var getVersion = function(label){
      var exp = new RegExp(label + '/([^\\s\\_\\-]+)');
      label = (agent.match(exp)||[])[1];
      return label || false;
  };

  var result = {
      os: function(){ //os system
          var bIsIpad = agent.match(/ipad/i) == "ipad";
          var bIsIphoneOs = agent.match(/iphone os/i) == "iphone os";
          var bIsMidp = agent.match(/midp/i) == "midp";
          var bIsUc7 = agent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
          var bIsUc = agent.match(/ucweb/i) == "ucweb";
          var bIsAndroid = agent.match(/android/i) == "android";
          var bIsCE = agent.match(/windows ce/i) == "windows ce";
          var bIsWM = agent.match(/windows mobile/i) == "windows mobile";
          var bIsLinux = agent.match(/linux/i) == 'linux';
          if(bIsIpad || bIsIphoneOs){
              return 'ios';
          }else if(bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM){
              return 'android';
          }else if(bIsLinux){
              return 'linux';
          }else {
              return 'windows';
          }
      }()
      ,ff : function(){
          return agent.indexOf('firefox') > -1;
      }()
      ,safari : function(){
          return agent.indexOf('safari') > -1 && agent.indexOf('chrome') == -1;
      }()
      ,chrome : function(){
          return agent.indexOf('chrome') > -1 && agent.indexOf('safari') > -1 && agent.indexOf('edge') < 0;
      }()
      ,opera : function(){
          return agent.indexOf('opera') > -1;
      }()
      ,weixin: getVersion('micromessenger')  //是否微信
  };
  //mobile device
  result.android = result.os === 'android';///android/.test(agent);
  result.ios = result.os === 'ios';
  return result;
};

var useragent$1 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  devices: devices,
  getRandom: getRandom,
  useragent: useragent
});

//download file from website

class download extends EventEmitter{

  /**
   * class constructor
   * @param {String} url download url
   * @param {String} storeFilePath store filepath after download success
   */
  constructor(url,storeFilePath,options) { 
    super();
    this.url = url;
    this.storeFilePath = typeof storeFilePath == 'object' ? '' : storeFilePath;
    this.options = typeof storeFilePath == 'object' ? storeFilePath : options || {};
    return this;
  }
  setOptions(options) {
    this.options = options;
    return this;
  }
  setUrl(url) {
    this.url = url;
    return this;
  }
  setRandomUserAgent() {
    this.options = Object.assign(this.options || {}, {
      'User-Agent' : getRandom()
    });
    return this;
  }
  async request() {
    return await fetch(this.url, this.options);
  }
  //get json data from url
  async json() {
    const res = await this.request();
    return await res.json();
  }
  //get html from url
  async html() {
    const res = await this.request();
    return await res.text();
  }
  //download file
  async download() {
    if (!this.storeFilePath) {
      throw new Error('download - storeFilePath : parameter is not found')
    }
    let that = this;
    return new Promise(async (r, j) => {
      const response = await that.request();
      let fileSize = response.headers.get('content-length');
      mkdir(path.dirname(this.storeFilePath));
      let cs = fs.createWriteStream(this.storeFilePath);
      let sendCount = 0;
      let lastTime = +new Date();
      let start = lastTime;
      let lastCount = 0;
      response.body.on('data', d => {
        sendCount += d.length;
        let cur = +new Date();
        let diff = Math.max(cur - lastTime, 1);
        let speed = '';
        if (diff > 1000) {//unreach 1000 ms.
          speed = humanSize((sendCount - lastCount) / diff * 1000) + '/s';  
          lastCount = sendCount;
          lastTime = cur;
        }
        that.emit('progress', {
          total: fileSize,
          send: sendCount,
          speed : speed,
          totalStr: humanSize(fileSize),
          sendStr: humanSize(sendCount)
        });
      });
      response.body.on('error', err => {
        j(err);
      });
      response.body.on('end', () => {
        //calculate average download speed
        let cur = +new Date();
        let diff = cur - start;
        let averageSize = humanSize(fileSize / diff * 1000)+'/s';
        r({
          total: fileSize,
          speed: averageSize,
          time : diff / 1000,
          totalStr : humanSize(fileSize)
        });
      });
      response.body.pipe(cs);
    })
  }
  /**
   * download m3u8 and ts file,collapse ts to mp4
   * @param {Number} threads default 1.
   */
  async downloadM3u8(threads=1) {
    //get m3u8 file 
    let that = this;
    let ins = await this.request();
    let m3u8Content = await ins.text();
    
    let fileList = [];
    let list = m3u8Content.split('\n').filter(t => {
      return t[0] != '#';
    }).filter(t => {
      return t != '';
    }).map(t => {
      // let tspath = path.normalize(path.join(folder, path.basename(t)));
      let realName = t.split('?')[0];
      let tspath = path.basename(realName);
      tspath = tspath.replace(/\\/g, '/');
      fileList.push(tspath);
      return t.indexOf('http') > -1 ? t :
        path.dirname(that.url) + '/' + t;
    });
    //create ts folder ,and download all 
    let tsFolderName = path.basename(that.storeFilePath,path.extname(that.storeFilePath)) + '_m3u8';
    let tsFolder = path.join(path.dirname(that.storeFilePath), tsFolderName);
    mkdir(tsFolder);
    for (let i = 0; i < fileList.length;i+=threads){
      let downloadArr = fileList.slice(i, i + threads);
      //
      let downloadFn = downloadArr.map((t,m) => {
        return (function (tsFileName,index) {
          return new Promise(async (r, j) => {
            let tsFilePath = path.join(tsFolder, tsFileName);
            if (!existsFile(tsFilePath)) {
              let tempIns = await fetch(list[index], that.options);//use options of m3u8
              tempIns.body.on('end', () => {
                r();
              });
              tempIns.body.on('error', err => {
                j(err);
              });
              tempIns.body.pipe(fs.createWriteStream(tsFilePath));
            } else {
              r();
            }
          });
        })(t,m);
      });

      //start download
      await Promise.all(downloadFn);

    }

    //start collapse ts file to mp4.
    //write concat list to collapseFile
    let collapseFile = path.join(tsFolder, 'index.txt');

    fs.writeFileSync(collapseFile, fileList.map(t => {
      return 'file ' + t + '';
    }).join('\r\n'));
    await collapse(collapseFile, that.storeFilePath);
    //remove ts folder
    deldir(tsFolder);
    return true;
  }
  //send data by post method
  async post(data) {
    this.options.method = 'POST';
    this.options.body = JSON.stringify(data);
    this.options.headers = Object.assign(this.options.headers || {}, {
      'Content-Type': 'application/json'
    });
    let response = await this.request();
    let rst = await response.json();
    return rst;
  }
}

//send simple message by dingtalk / email / bark ...

const dingding = (token, secret, text, desc) => {
  return new Promise(async (resolve, reject) => {
    let url = `https://oapi.dingtalk.com/robot/send?access_token=${token}`;
    let data = {
      msgtype: 'text',
      text: {
        content: `${text}\n\n${desc}`,
      },
    };
    if (token && secret) {
      const dateNow = Date.now();
      const hmac = crypto.createHmac('sha256', secret);
      hmac.update(`${dateNow}\n${secret}`);
      const result = encodeURIComponent(hmac.digest('base64'));
      url = `${url}&timestamp=${dateNow}&sign=${result}`;

    }
    let ins = new download(url);
    try {
      let rst = await ins.post(data);
      if (rst.errcode === 0) {
        resolve(true);
      } else {
        reject(rst.errmsg);
      }
    } catch (err) {
      reject('Send message failed : Dingtalk');
    }
  });

};

const mail = async (smtpHost,port,baseUser,basePass,userList,title,content) => {
  // create reusable transporter object using the default SMTP transport
  var transporter = nodemailer.createTransport({
    pool: true,
    host: smtpHost,
    port: port,
    secure: true,
    auth: {
      user: baseUser,
      pass: basePass
    },
    tls: {
      // do not fail on invalid certs
      rejectUnauthorized: false,
    },
  });

  // setup e-mail data with unicode symbols
  var mailOptions = {
    from: 'BUG<'+baseUser+'>', // sender address
    to: userList.join(','), // list of receivers
    subject: title, // Subject line
    html: content // html body
  };
  let info;
  try {
    info = await transporter.sendMail(mailOptions);  
  } catch (err) {
    console.log(err);
  }
  return info;
};

var message = /*#__PURE__*/Object.freeze({
  __proto__: null,
  dingding: dingding,
  mail: mail
});

//compress picture

/**
 * compress pictures
 * @param {String} filePath 
 * @param {Number} compressValue qulity.1-100 . more bigger more clarity
 * @param {String} outputFilePath 
 */
const compress = async (filePath, compressValue, outputFilePath) => {
  outputFilePath = outputFilePath || filePath;//compress filePath if outputFilePath is undefined
  let extname = path.extname(outputFilePath);
  extname = extname.toLowerCase();
  let buf;
  switch (extname) {
    case '.png':
      let level = Math.ceil(10 - compressValue / 10);
      buf = await sharp(filePath).png({
        compressionLevel: level
      }).toBuffer();
      break;
    case '.jpg':
    case '.jpeg':
      buf = await sharp(filePath).jpeg({
        quality: compressValue
      }).toBuffer();
      break;
    case '.webp':
      buf = await sharp(filePath).webp({
        quality: compressValue
      }).toBuffer();
      break;
    case '.gif':
      buf = await sharp(filePath, { animated: true, limitInputPixels: false }).gif({
        colours: Math.max(2, Math.min(256, (Math.floor(compressValue / 100 * 256))))
      }).toBuffer();
    default:
      buf = await sharp(filePath).toBuffer();
  }
  fs.writeFileSync(outputFilePath, buf);
  return true;
};

const resize = async (filePath, width, height, storeFilePath) => {
  storeFilePath = storeFilePath || filePath;
  let resizeOptions = { width, height, options: { fit: 'fill' } };
  let buf = await sharp(filePath).resize(resizeOptions).toBuffer();
  fs.writeFileSync(storeFilePath, buf);
  return true;
};

var image = /*#__PURE__*/Object.freeze({
  __proto__: null,
  compress: compress,
  resize: resize
});

//log4js
let defaultConfig = {
  type: 'dateFile',
  filename: path.resolve('logs/info.log'),
  encoding: 'utf-8',
  layout: {
    type: 'pattern',
    pattern: '%d %p [%c] %h %m'
  },
  //split by day
  pattern: 'yyyy-MM-dd',
  // ends as '.log' 
  keepFileExt: true,
  //insert the date at the tail of name
  alwaysIncludePattern: true,
};
const config = {
  appenders: {
    default: defaultConfig,
    //debug appender
    debugAppender: {
      ...defaultConfig,
      filename: path.resolve('logs/debug.log'),
    },
    console: {
      type : 'console'
    },
    //debug filter
    debugFilter: {
      type: 'logLevelFilter',
      appender: 'debugAppender', // assign a specific appender
      level: 'debug', // lowest log level to catch
      maxLevel: 'debug', //highest log level to catch
    },
    //info filter
    infoFilter: {
      type: 'logLevelFilter',
      appender: 'default',
      level: 'info',
      maxLevel: 'info',
    },
    //warn filter
    warnFilter: {
      type: 'logLevelFilter',
      appender: 'default',
      level: 'warn',
      maxLevel: 'warn',
    },
    errorAppender: {
      ...defaultConfig,
      filename: path.resolve('logs/error.log'),
    },
    //error filter
    errorFilter: {
      type: 'logLevelFilter',
      appender: 'errorAppender',
      level: 'error',
      maxLevel: 'fatal',
    },
  },

  categories: {
    default: {   //default is a must
      appenders: ['console','infoFilter', 'warnFilter', 'errorFilter', 'debugFilter'],
      level: 'debug',
    }
  }
};

/**
 * get logger instance
 * @param {String} name default|error
 */
const getLogger = (name) => {
  log4js.configure(config);
  return log4js.getLogger(name)
};

exports.common = common;
exports.download = download;
exports.ffmpeg = ffmpeg;
exports.fileutil = fileutil;
exports.getLogger = getLogger;
exports.image = image;
exports.message = message;
exports.useragent = useragent$1;
